package main;

// ToDo getNeighbours und addNeighbours beachten!!!!!
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jdom2.JDOMException;
import org.resotc.dao.CentroidDAO;
import org.resotc.dao.ConfigDAO;
import org.resotc.dao.DisturbancesDAO;
import org.resotc.dao.IntersectionDAO;
import org.resotc.dao.LaneDAO;
import org.resotc.dao.QueueDAO;
import org.resotc.dao.StreetDAO;
import org.resotc.dao.TrackDAO;
import org.resotc.model.Centroid;
import org.resotc.model.Element;
import org.resotc.model.ElementType;
import org.resotc.model.Intersection;
import org.resotc.model.Lane;
import org.resotc.model.NeighbourOrientation;
import org.resotc.model.Queue;
import org.resotc.model.Street;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import participants.CarImpl;
import sim.engine.SimState;
import sim.engine.Steppable;
import sim.field.continuous.Continuous2D;
import sim.field.network.Network;
import sim.util.Double2D;
import config.Configuration;
import dao.impl.CentroidDAOImpl;
import dao.impl.ConfigDAOImpl;
import dao.impl.DisturbancesDAOImpl;
import dao.impl.IntersectionDAOImpl;
import dao.impl.LaneDAOImpl;
import dao.impl.QueueDAOImpl;
import dao.impl.StreetDAOImpl;
import dao.impl.TrackDAOImpl;

public class ResOTCModel extends SimState {

	private Logger log = LoggerFactory.getLogger(ResOTCModel.class);

	private HashMap<Integer, Intersection> intersections;
	private HashMap<Integer, Queue> queues;
	private HashMap<Integer, Street> streetHashMap;
	private Map<Integer, Lane> lanesHashMap;
	HashMap<NeighbourOrientation, Element> neighbors;
	HashMap<Integer, Centroid> centroids;
	ArrayList<Element> elements = new ArrayList<Element>();
	public int numCars = 10;
	private Continuous2D environment;
	private Network lanes = new Network(false);
	private Network network = new Network(false);
	public Continuous2D cars;
	public Continuous2D laneContinuous2D;
	public Continuous2D trafficLightContinuous2D;
	CarImpl carImpl;
	private IntersectionDAO intersectionDAO;
	private CentroidDAO centroidDAO;
	private LaneDAO laneDAO;
	private StreetDAO streetDAO;
	private TrackDAO trackDAO;
	private QueueDAO queueDAO;
	private ConfigDAO configDAO;
	private ArrayList<Lane> laneList;
	private ArrayList<Queue> trafficQueueList;
	private DisturbancesDAO disturbancesDAO;
	double offset = 0.1;

	public ResOTCModel(long seed) {
		super(seed);
		try {
			intersectionDAO = new IntersectionDAOImpl();
		} catch (JDOMException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			centroidDAO = new CentroidDAOImpl();
		} catch (JDOMException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			trackDAO = new TrackDAOImpl();
		} catch (JDOMException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			disturbancesDAO = new DisturbancesDAOImpl();
		} catch (JDOMException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		} catch (IOException e2) {
			// TODO Auto-generated catch block
			e2.printStackTrace();
		}

		try {
			laneDAO = new LaneDAOImpl(trackDAO, intersectionDAO, centroidDAO,
					disturbancesDAO);
		} catch (JDOMException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			streetDAO = new StreetDAOImpl(laneDAO);
		} catch (JDOMException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			configDAO = new ConfigDAOImpl();
		} catch (JDOMException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			queueDAO = new QueueDAOImpl(trackDAO, streetDAO, intersectionDAO);
		} catch (JDOMException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}

	public static void main(String[] args) {
		SimState state = new ResOTCModel(System.currentTimeMillis());
		state.start();
		do {
			if (state.schedule.getSteps() % 1000 == 0) {
				System.out.println("Currently executing step: "
						+ state.schedule.getSteps());
			}
			if (!state.schedule.step(state)) {
				break;
			}
		} while (state.schedule.getSteps() < Configuration.numberOfSteps);
		state.finish();
		System.exit(0);
	}

	public int getNumCars() {
		return numCars;
	}

	public void setNumCars(int numCars) {
		this.numCars = numCars;
	}

	public Continuous2D getEnvironment() {
		return environment;
	}

	/**
	 * Returns the network containing all streets.
	 * 
	 * @return
	 */
	public Network getLanes() {
		return lanes;
	}

	public void start() {
		super.start();
		laneList = new ArrayList<Lane>();
		trafficQueueList = new ArrayList<Queue>();

		// Injector injector = Guice.createInjector(new ResOTCModule());
		// intersectionDAO = injector.getInstance(IntersectionDAOImpl.class);
		// centroidDAO = injector.getInstance(CentroidDAOImpl.class);
		// laneDAO = injector.getInstance(LaneDAOImpl.class);
		// streetDAO = injector.getInstance(StreetDAOImpl.class);

		// insert cars
		cars = new Continuous2D(Configuration.discretisation,
				Configuration.width, Configuration.heigth);
		laneContinuous2D = new Continuous2D(Configuration.discretisation,
				Configuration.width, Configuration.heigth);
		trafficLightContinuous2D = new Continuous2D(
				Configuration.discretisation, Configuration.width,
				Configuration.heigth);

		// insert intersections
		environment = new Continuous2D(Configuration.discretisation,
				Configuration.width, Configuration.heigth);
		environment.clear();

		// bring the centroids

		intersections = intersectionDAO.getIntersections();
		List<Intersection> intersectionList = new ArrayList<Intersection>(
				intersections.values());

		centroids = centroidDAO.getCentroids();

		List<Centroid> centroidList = new ArrayList<Centroid>(
				centroids.values());
		lanesHashMap = laneDAO.getLanes();
		streetHashMap = streetDAO.getStreets();
		List<Street> streetList = new ArrayList<Street>(streetHashMap.values());
		queues = queueDAO.getQueues();
		List<Queue> queueList = new ArrayList<Queue>(queues.values());
		// put intersections into the environment
		for (Intersection intersection : intersectionList) {
			environment.setObjectLocation(intersection,
					intersection.getPosition());
			lanes.addNode(intersection);
			intersection.initializeQueues(queueDAO.getQueues());
			intersection.setPhaseSystem(configDAO.getConfig(
					intersection.getId(), ElementType.INTERSECTION)
					.getPhasenlist());
			elements.add((Element) intersection);
			schedule.scheduleRepeating((Steppable) intersection);
		}

		// put all centroids into the environment
		for (Centroid centroid : centroidList) {
			environment.setObjectLocation(centroid,
					new Double2D(centroid.getX(), centroid.getY()));
			lanes.addNode(centroid);
			elements.add((Element) centroid);
			schedule.scheduleRepeating((Steppable) centroid);
			centroid.setIndividualPerHour(configDAO.getConfig(centroid.getId(),
					ElementType.CENTROID).getIndividualPerHour());
			centroid.setMode(configDAO.getConfig(centroid.getId(),
					ElementType.CENTROID).getMode());
		}
		// determine neighbours:
		for (Street street : streetList) {

			HashMap<Integer, Lane> lanesInStreet;
			lanesInStreet = street.getLanes();
			for (Lane lane : lanesInStreet.values()) {

				Element predeccessor = lane.getPredecessor();
				Element successor = lane.getSuccessor();

				ElementType preType = predeccessor.getElementType();
				ElementType sucType = successor.getElementType();
				Centroid fromCentroid;
				Centroid toCentroid;
				Intersection fromIntersection;
				Intersection toIntersection;
				Double2D myPrePos = predeccessor.getPosition();
				Double2D mySuccPos = successor.getPosition();
				NeighbourOrientation orNeighbour = NeighbourOrientation
						.getRelativeNeighbourOrientation(myPrePos, mySuccPos);
				NeighbourOrientation revNeighbour = NeighbourOrientation
						.invertOrientation(orNeighbour);

				if (preType == ElementType.CENTROID
						&& sucType == ElementType.CENTROID) {
					fromCentroid = centroids.get(predeccessor.getId());
					toCentroid = centroids.get(successor.getId());
					lanes.addEdge(fromCentroid, toCentroid, 1);
					fromCentroid.addNeighbour(orNeighbour, toCentroid, street);
					toCentroid.addNeighbour(revNeighbour, fromCentroid, street);

				}
				if (preType == ElementType.CENTROID
						&& sucType == ElementType.INTERSECTION) {

					fromCentroid = centroids.get(predeccessor.getId());
					toIntersection = intersections.get(successor.getId());
					lanes.addEdge(fromCentroid, toIntersection, 1);
					fromCentroid.addNeighbour(orNeighbour, toIntersection,
							street);
					// toIntersection.addNeighbour(revNeighbour, fromCentroid,
					// street);
				}
				if (preType == ElementType.INTERSECTION
						&& sucType == ElementType.CENTROID) {
					fromIntersection = intersections.get(predeccessor.getId());
					toCentroid = centroids.get(successor.getId());
					lanes.addEdge(fromIntersection, toCentroid, 1);
					// fromIntersection.addNeighbour(orNeighbour, toCentroid,
					// street);
					toCentroid.addNeighbour(revNeighbour, fromIntersection,
							street);
				}
				if (preType == ElementType.INTERSECTION
						&& sucType == ElementType.INTERSECTION) {
					fromIntersection = intersections.get(predeccessor.getId());
					toIntersection = intersections.get(successor.getId());
					lanes.addEdge(fromIntersection, toIntersection, 1);
					// fromIntersection.addNeighbour(orNeighbour,
					// toIntersection, street);
					// toIntersection.addNeighbour(revNeighbour,
					// fromIntersection, street);
				}
				schedule.scheduleRepeating((Steppable) lane);
				for (Queue queue : queueList) {
					schedule.scheduleRepeating((Steppable) queue);
					if (queue.getComponentType() == ElementType.INTERSECTION) {
						Intersection intersection = intersections.get(queue
								.getComponentTypeId());
						HashMap<Integer, Lane> queueLanes = queue.getStreet()
								.getLanes();
						for (Lane queueLane : queueLanes.values()) {

							Double2D position;
							if (queueLane.getFromType() == ElementType.CENTROID) {
								position = centroids.get(queueLane.getFromId())
										.getPosition();
							} else if (queueLane.getToType() == ElementType.CENTROID) {
								position = centroids.get(queueLane.getToId())
										.getPosition();
							} else {

								position = intersections.get(
										queueLane.getFromId()).getPosition();

								if (position.equals(intersection.getPosition())) {
									position = intersections.get(
											queueLane.getToId()).getPosition();

								}
							}
							HashMap<Integer, Double2D> lightsPos = queue
									.getTrafficLight().calcLightPos(
											intersection.getPosition(),
											position);
							System.out.println("setze");
							queue.getTrafficLight().SetLightPos(lightsPos);
							

							break;
						}
					}
					if(queue.getComponentType() == ElementType.INTERSECTION) {
						trafficQueueList.add(queue);
					}
				}
			}

		}

		// new drawing:
		for (Street street : streetDAO.getStreets().values()) {
			int lanes = street.getLanesCount();
			int multiplicator = 1;

			List<Lane> laneListTEST = new ArrayList<Lane>(street.getLanes()
					.values());

			for (Lane lane : street.getLanes().values()) {
				Double2D pre = lane.getPredecessor().getPosition();
				Double2D suc = lane.getSuccessor().getPosition();
				lane.setCenter(pre, suc);

				Double2D center = lane.getCenter();

				Double2D p = getTurningPoint(lane);

				// Turn point p 90 degrees:
				Double2D pTurn = rotatePoint(p, center, 90);
				Double2D shiftVec = getShiftingVector(center, pTurn);

				lane.setPredecessor2D(new Double2D(pre.x + multiplicator
						* shiftVec.x, pre.y + multiplicator * shiftVec.y));
				lane.setSuccessor2D(new Double2D(suc.x + multiplicator
						* shiftVec.x, suc.y + multiplicator * shiftVec.y));

				// calculate vector from center to turned point:
				Double2D newPre = lane.getPredecessor2D();
				Double2D newSuc = lane.getSuccessor2D();
				lane.setCenter(newPre, newSuc);
				Double2D newCenter = lane.getCenter();

				// mount shape:
				// therefore it is the same to do as with the center before
				// but here the pre and suc pos must be rotated

				// 5. Mount shape:
				// think about, how to realize
				// coordinates are at pre and suc
				// they may be constructed
				// 1st parameters are the right upper corner
				// 2nd left upper corner
				// 3rd left lower corner
				// 4th right lower corner
				//
				// take the turned point
				//
				// width should be declared anyway

				Double2D turnSucPnt = getTurningPointFromEnd(
						lane.getSuccessor2D(), lane);
				Double2D turnPrePnt = getTurningPointFromEnd(
						lane.getPredecessor2D(), lane);

				Double2D lftDwn = rotatePoint(turnSucPnt, newSuc, 90);
				Double2D rgtDwn = rotatePoint(turnPrePnt, newPre, 90);
				Double2D lftUp = rotatePoint(turnSucPnt, newSuc, -90);
				Double2D rgtUp = rotatePoint(turnPrePnt, newPre, -90);

				// calculation
				lftDwn = new Double2D(newCenter.x - lftDwn.x, newCenter.y
						- lftDwn.y);
				rgtDwn = new Double2D(newCenter.x - rgtDwn.x, newCenter.y
						- rgtDwn.y);
				lftUp = new Double2D(newCenter.x - lftUp.x, newCenter.y
						- lftUp.y);
				rgtUp = new Double2D(newCenter.x - rgtUp.x, newCenter.y
						- rgtUp.y);

				double[] xArray = { rgtDwn.x, lftUp.x, lftDwn.x, rgtUp.x };
				double[] yArray = { rgtDwn.y, lftUp.y, lftDwn.y, rgtUp.y };

				lane.setShapeXCoordinates(xArray);
				lane.setShapeYCoordinates(yArray);

				laneList.add(lane);
				environment.setObjectLocation(lane, newCenter);

				for (Lane laneElement : laneListTEST) {
					if (lane.getPredecessor().getPosition()
							.equals(laneElement.getPredecessor().getPosition())) {
						multiplicator++;
					}
				}
			}
		}
	}

	public IntersectionDAO getIntersectionDAO() {
		return intersectionDAO;
	}

	public CentroidDAO getCentroidDAO() {
		return centroidDAO;
	}

	public LaneDAO getLaneDAO() {
		return laneDAO;
	}

	public StreetDAO getStreetDAO() {
		return streetDAO;
	}

	public TrackDAO getTrackDAO() {
		return trackDAO;
	}

	public QueueDAO getQueueDAO() {
		return queueDAO;
	}

	public ConfigDAO getConfigDAO() {
		return configDAO;
	}

	public Double2D rotatePoint(Double2D p, Double2D anchor, double theta) {
		double thetaRad = Math.toRadians(theta);
		double x = Math.round(anchor.x
				+ (Math.cos(thetaRad) * (p.x - anchor.x))
				- (Math.sin(thetaRad) * (p.y - anchor.y)));
		double y = Math.round(anchor.y
				+ (Math.sin(thetaRad) * (p.x - anchor.x))
				+ (Math.cos(thetaRad) * (p.y - anchor.y)));

		return new Double2D(x, y);
	}

	public Double2D getTurningPoint(Lane lane) {
		double distance = 50;
		Double2D pre = lane.getPredecessor().getPosition();
		Double2D suc = lane.getSuccessor().getPosition();

		// Calculate original center
		lane.setCenter(pre, suc);
		Double2D center = lane.getCenter();

		// center is always set 90 degrees to a lane reference point
		// 1. calculate gradient
		// g:x = (x, y) + sigma * (gradient.x, gradient.y)
		Double2D gradient = new Double2D(pre.x - center.x, pre.y - center.y);

		// normalize gradient:
		double vectorAmount = Math.sqrt(gradient.x * gradient.x + gradient.y
				* gradient.y);
		Double2D normGradient = new Double2D((1 / vectorAmount) * gradient.x,
				(1 / vectorAmount) * gradient.y);

		// 2. Calculate sigma
		double sigma = Math.round(Math.sqrt((distance * distance)
				/ ((gradient.x * gradient.x) + (gradient.y * gradient.y))));

		// 3. Calculate point on the line
		Double2D p = new Double2D(center.x + (sigma * normGradient.x), center.y
				+ (sigma * normGradient.y));

		return p;
	}

	public Double2D getShiftingVector(Double2D center, Double2D pTurn) {
		Double2D shiftVector = new Double2D(-(pTurn.x - center.x),
				-(pTurn.y - center.y));
		return shiftVector;
	}

	public Double2D getTurningPointFromEnd(Double2D endP, Lane lane) {
		double distance = 1;
		Double2D center = lane.getCenter();

		Double2D gradient = new Double2D(center.x - endP.x, center.y - endP.y);
		double vectorAmount = Math.sqrt(gradient.x * gradient.x + gradient.y
				* gradient.y);
		Double2D normGradient = new Double2D((1 / vectorAmount) * gradient.x,
				(1 / vectorAmount) * gradient.y);
		double sigma = Math
				.round(Math
						.sqrt((distance * distance)
								/ ((normGradient.x * normGradient.x) + (normGradient.y * normGradient.y))));
		Double2D p = new Double2D((endP.x + (sigma * normGradient.x)),
				(endP.y + (sigma * normGradient.y)));

		return p;
	}

	public ArrayList<Queue> getTrafficQueueList() {
		return trafficQueueList;
	}

	public ArrayList<Lane> getLaneList() {
		return laneList;
	}
}